package com.xja.shoppingmall.filter;

import com.alibaba.fastjson.JSON;
import com.xja.shoppingmall.constant.CommonConstant;
import com.xja.shoppingmall.constant.UrlGatewayConstant;
import com.xja.shoppingmall.util.TokenParseUtil;
import com.xja.shoppingmall.vo.JwtToken;
import com.xja.shoppingmall.vo.LoginUserInfo;
import com.xja.shoppingmall.vo.UsernameAndPassword;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author CHENCHEN
 * @title   全局登录鉴权过滤器
 * @createTime 2021-12-1219:48
 */
@Slf4j
@Component
public class GlobalLoginOrRegisterFilter implements GlobalFilter, Ordered {

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 登录、鉴权
     * 1、登录，去授权中心拿到Token并返回客户端
     * 2、如果是访问其他的服务，需要鉴权，没有权限返回401
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        //如果是登录
        if(request.getURI().getPath().contains(UrlGatewayConstant.LOGIN_URL)){
            log.info("request uri is login");
            //去授权中心获取token
            String token = getTokenFromAuthorityCenter(request, UrlGatewayConstant.AUTHORITY_CREATE_TOKEN_URL_FORMAT);
            response.getHeaders().add(
                    CommonConstant.JWT_USER_INFO_KEY,
                    token == null ? "null":token
            );
            response.setStatusCode(HttpStatus.OK);
            return response.setComplete();
        }
        if(request.getURI().getPath().contains(UrlGatewayConstant.REGISTER_URL)){
            // 解析通过, 则放行
            return chain.filter(exchange);
        }
        //访问其他服务
        log.info("request uri is others server");
        HttpHeaders headers = request.getHeaders();
        String token = headers.getFirst(CommonConstant.JWT_USER_INFO_KEY);
        LoginUserInfo loginUserInfo = null;
        try {
            loginUserInfo = TokenParseUtil.parseUserInfoFromToken(token);
        } catch (Exception ex) {
            log.error("parse user info from token error: [{}]", ex.getMessage(), ex);
        }
        // 获取不到登录用户信息, 返回 401
        if (null == loginUserInfo) {
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }
        // 解析通过, 则放行
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return HIGHEST_PRECEDENCE + 2;
    }

    /**
     * 从post请求体中获取到请求数据
     * @param request
     * @return
     */
    private String paseBodyFromRequest(ServerHttpRequest request){
        //获取请求体
        Flux<DataBuffer> body = request.getBody();
        AtomicReference<String> bodyRef = new AtomicReference<>();
        //订阅缓冲区去消费请求体中的数据
        body.subscribe(dataBuffer -> {
            CharBuffer charBuffer = StandardCharsets.UTF_8.decode(dataBuffer.asByteBuffer());
            //一定要使用DataBufferUtils.release(dataBuffer)释放掉，否则会出现内存泄漏
            DataBufferUtils.release(dataBuffer);
            bodyRef.set(charBuffer.toString());
        });
        //获取request body
        return bodyRef.get();
    }

    /**
     * 从授权中心获取Token
     * @param request
     * @param urlFormat
     * @return
     */
    private String getTokenFromAuthorityCenter(ServerHttpRequest request, String urlFormat){
        //RestTemplate 调用Ribbon进行负载获取服务实例信息
        ServiceInstance serviceInstance = loadBalancerClient.choose(CommonConstant.AUTHORITY_CENTER_SERVICE_ID);
        log.info("Nacos Client Info : [{}], [{}], [{}]", serviceInstance.getHost(), serviceInstance.getPort(), JSON.toJSONString(serviceInstance.getMetadata()));
        String requestUrl = String.format(urlFormat, serviceInstance.getHost(), serviceInstance.getPort());
        UsernameAndPassword requestBody = JSON.parseObject(paseBodyFromRequest(request), UsernameAndPassword.class);
        log.info("login request url and body : [{}], [{}]", requestUrl, requestBody);
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        JwtToken jwtToken = restTemplate.postForObject(requestUrl, new HttpEntity<>(JSON.toJSONString(requestBody), httpHeaders), JwtToken.class);
        if(jwtToken != null){
            return jwtToken.getToken();
        }
        return null;
    }
}
